Skip to content

feat(runtime): convert evaluate results from Chrome's deep serialization in one call - #677

Merged
route merged 1 commit into
mainfrom
feat/deep-serialization
Oct 23, 2026
Merged

route merged 1 commit into
mainfrom
feat/deep-serialization

Conversation

@route

@route route commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

evaluate used to walk every result object by object: Runtime.getProperties per nested object, a separate cycle check, and a DOM.describeNode per node. It now passes serializationOptions: { serialization: "deep" } to Runtime.callFunctionOn and converts the single deepSerializedValue Chrome returns.

Performance

Same page, average of 20 runs:

Case main this branch
css("li"), 300 elements 303 commands, 64.1 ms 1 command, 6.2 ms
50 small nested objects 103 commands, 22.2 ms 1 command, 1.2 ms
table rows as data (50×2) 53 commands, 10.7 ms 1 command, 0.7 ms
1000 numbers 3 commands, 5.1 ms 1 command, 2.3 ms
at_css 2 commands 1 command

Behavior changes

Fixed:

  • null and undefined stay in arrays: [1, null, 3] returned [1, 3]
  • NaN, Infinity, -0 and BigInt are returned instead of nil
  • objects with getters no longer crash with NoMethodError

Changed:

  • dates come back as ISO 8601 strings instead of the browser's local toString
  • a cyclic result is a regular Hash/Array with CyclicObject only at the reference back to an enclosing object, instead of the whole value being CyclicObject; window is {}
  • Node is built from the serialized node's properties and gets backend_node_id, node_type and frame_id readers; Node#description is the full DOM.describeNode result, fetched on first use. Node.new(frame, properties) replaces Node.new(frame, target_id, description)
  • Node#node_id uses DOM.pushNodesByBackendIdsToFrontend, since nested nodes come without an object id
  • a returned arguments object is a Hash with "0", "1" keys, typed arrays are {}

Map, Set and RegExp stay {} as before.

…ion in one call

evaluate walked every nested object with Runtime.getProperties plus a separate cycle check, and described every node
with DOM.describeNode, one CDP call per object or node. Now Runtime.callFunctionOn returns the whole result with
serializationOptions: deep, so css on 300 elements takes 1 command instead of 303.

Null and undefined stay in arrays, NaN, Infinity, -0 and BigInt are returned instead of nil, getters no longer crash,
dates are ISO strings. A cyclic result is a regular Hash/Array with CyclicObject only at the back-reference instead of
CyclicObject for the whole value.

Node is built from the serialized node's properties and gets backend_node_id, node_type and frame_id readers;
description is fetched with DOM.describeNode on first use, node_id is pushed by backendNodeId.
@route
route force-pushed the feat/deep-serialization branch from 12b6e9f to feaf09f Compare October 23, 2026 10:39
@route
route merged commit 4707d81 into main Oct 23, 2026
6 checks passed
@route
route deleted the feat/deep-serialization branch October 23, 2026 10:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant